/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
 
This file is part of Quake III Arena source code.
 
Quake III Arena source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
 
Quake III Arena source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
 
You should have received a copy of the GNU General Public License
along with Foobar; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
*/

/*****************************************************************************
 * name:		snd_mem.c
 *
 * desc:		sound caching
 *
 * $Archive: /code/client/snd_mem.c $
 *
 *****************************************************************************/

#include "snd_local.h"

#define DEF_COMSOUNDMEGS "8"

/*
===============================================================================
 
memory management
 
===============================================================================
*/

static	sndBuffer	*buffer = NULL;
static	sndBuffer	*freelist = NULL;
static	int inUse = 0;
static	int totalInUse = 0;

short *sfxScratchBuffer = NULL;
sfx_t *sfxScratchPointer = NULL;
int	   sfxScratchIndex = 0;

void	SND_free(sndBuffer *v)
{
    *(sndBuffer **)v = freelist;
    freelist = (sndBuffer*)v;
    inUse += sizeof(sndBuffer);
}

sndBuffer*	SND_malloc()
{
    sndBuffer *v;
redo:
    if (freelist == NULL)
    {
        S_FreeOldestSound();
        goto redo;
    }

    inUse -= sizeof(sndBuffer);
    totalInUse += sizeof(sndBuffer);

    v = freelist;
    freelist = *(sndBuffer **)freelist;
    v->next = NULL;
    return v;
}

void SND_setup()
{
    sndBuffer *p, *q;
    cvar_t	*cv;
    int scs;

    cv = Cvar_Get( "com_soundMegs", DEF_COMSOUNDMEGS, CVAR_LATCH | CVAR_ARCHIVE );

    scs = (cv->integer*1536);

    buffer = (sndBuffer*)malloc(scs*sizeof(sndBuffer) );
    // allocate the stack based hunk allocator
    sfxScratchBuffer = (short *)malloc(SND_CHUNK_SIZE * sizeof(short) * 4);	//Hunk_Alloc(SND_CHUNK_SIZE * sizeof(short) * 4);
    sfxScratchPointer = NULL;

    inUse = scs*sizeof(sndBuffer);
    p = buffer;
    ;
    q = p + scs;
    while (--q > p)
        *(sndBuffer **)q = q-1;

    *(sndBuffer **)q = NULL;
    freelist = p + scs - 1;

    Com_Printf("Sound memory manager started\n");
}

/*
===============================================================================
 
WAV loading
 
===============================================================================
*/

static	byte	*data_p;
static	byte 	*iff_end;
static	byte 	*last_chunk;
static	byte 	*iff_data;
static	int 	iff_chunk_len;

static short GetLittleShort(void)
{
    short val = 0;
    val = *data_p;
    val = val + (*(data_p+1)<<8);
    data_p += 2;
    return val;
}

static int GetLittleLong(void)
{
    int val = 0;
    val = *data_p;
    val = val + (*(data_p+1)<<8);
    val = val + (*(data_p+2)<<16);
    val = val + (*(data_p+3)<<24);
    data_p += 4;
    return val;
}

static void FindNextChunk(char *name)
{
    while (1)
    {
        data_p=last_chunk;

        if (data_p >= iff_end)
        {	// didn't find the chunk
            data_p = NULL;
            return;
        }

        data_p += 4;
        iff_chunk_len = GetLittleLong();
        if (iff_chunk_len < 0)
        {
            data_p = NULL;
            return;
        }
        data_p -= 8;
        last_chunk = data_p + 8 + ( (iff_chunk_len + 1) & ~1 );
        if (!strncmp((char *)data_p, name, 4))
            return;
    }
}

static void FindChunk(char *name)
{
    last_chunk = iff_data;
    FindNextChunk (name);
}

/*
============
GetWavinfo
============
*/
static wavinfo_t GetWavinfo (char *name, byte *wav, int wavlength)
{
    wavinfo_t	info;

    Com_Memset (&info, 0, sizeof(info));

    if (!wav)
        return info;

    iff_data = wav;
    iff_end = wav + wavlength;

// find "RIFF" chunk
    FindChunk("RIFF");
    if (!(data_p && !strncmp((char *)data_p+8, "WAVE", 4)))
    {
        Com_Printf("Missing RIFF/WAVE chunks\n");
        return info;
    }

// get "fmt " chunk
    iff_data = data_p + 12;
// DumpChunks ();

    FindChunk("fmt ");
    if (!data_p)
    {
        Com_Printf("Missing fmt chunk\n");
        return info;
    }
    data_p += 8;
    info.format = GetLittleShort();
    info.channels = GetLittleShort();
    info.rate = GetLittleLong();
    data_p += 4+2;
    info.width = GetLittleShort() / 8;

    if (info.format != 1)
    {
        Com_Printf("Microsoft PCM format only\n");
        return info;
    }


// find data chunk
    FindChunk("data");
    if (!data_p)
    {
        Com_Printf("Missing data chunk\n");
        return info;
    }

    data_p += 4;
    info.samples = GetLittleLong () / info.width;
    info.dataofs = data_p - wav;

    return info;
}


/*
================
ResampleSfx
 
resample / decimate to the current source rate
================
*/
static void ResampleSfx( sfx_t *sfx, int inrate, int inwidth, byte *data, qboolean compressed )
{
    int		outcount;
    int		srcsample;
    float	stepscale;
    int		i;
    int		sample, samplefrac, fracstep;
    int			part;
    sndBuffer	*chunk;

    stepscale = (float)inrate / dma.speed;	// this is usually 0.5, 1, or 2

    outcount = sfx->soundLength / stepscale;
    sfx->soundLength = outcount;

    samplefrac = 0;
    fracstep = stepscale * 256;
    chunk = sfx->soundData;

    for (i=0 ; i<outcount ; i++)
    {
        srcsample = samplefrac >> 8;
        samplefrac += fracstep;
        if( inwidth == 2 )
        {
            sample = LittleShort ( ((short *)data)[srcsample] );
        }
        else
        {
            sample = (int)( (unsigned char)(data[srcsample]) - 128) << 8;
        }
        part  = (i&(SND_CHUNK_SIZE-1));
        if (part == 0)
        {
            sndBuffer	*newchunk;
            newchunk = SND_malloc();
            if (chunk == NULL)
            {
                sfx->soundData = newchunk;
            }
            else
            {
                chunk->next = newchunk;
            }
            chunk = newchunk;
        }

        chunk->sndChunk[part] = sample;
    }
}

/*
================
ResampleSfx
 
resample / decimate to the current source rate
================
*/
static int ResampleSfxRaw( short *sfx, int inrate, int inwidth, int samples, byte *data )
{
    int			outcount;
    int			srcsample;
    float		stepscale;
    int			i;
    int			sample, samplefrac, fracstep;

    stepscale = (float)inrate / dma.speed;	// this is usually 0.5, 1, or 2

    outcount = samples / stepscale;

    samplefrac = 0;
    fracstep = (int)(stepscale * 256);

    for (i=0 ; i<outcount ; i++)
    {
        srcsample = samplefrac >> 8;
        samplefrac += fracstep;
        if( inwidth == 2 )
        {
            sample = LittleShort ( ((short *)data)[srcsample] );
        }
        else
        {
            sample = (int)( (unsigned char)(data[srcsample]) - 128) << 8;
        }
        sfx[i] = sample;
    }
    return outcount;
}


//=============================================================================

/*
==============
S_LoadSound
 
The filename may be different than sfx->name in the case
of a forced fallback of a player specific sound
==============
*/
qboolean S_LoadSound( sfx_t *sfx )
{
    byte	*data;
    short	*samples;
    wavinfo_t	info;
    int		size;

    // player specific sounds are never directly loaded
    if ( sfx->soundName[0] == '*')
    {
        return qfalse;
    }

    // load it in
    size = FS_ReadFile( sfx->soundName, (void **)&data );
    if ( !data )
    {
        return qfalse;
    }

    info = GetWavinfo( sfx->soundName, data, size );
    if ( info.channels != 1 )
    {
        Com_Printf ("%s is a stereo wav file\n", sfx->soundName);
        FS_FreeFile (data);
        return qfalse;
    }

    if ( info.width == 1 )
    {
        Com_DPrintf(S_COLOR_YELLOW "WARNING: %s is a 8 bit wav file\n", sfx->soundName);
    }

    if ( info.rate != 22050 )
    {
        Com_DPrintf(S_COLOR_YELLOW "WARNING: %s is not a 22kHz wav file\n", sfx->soundName);
    }

    samples = (short *)Hunk_AllocateTempMemory(info.samples * sizeof(short) * 2);

    sfx->lastTimeUsed = Com_Milliseconds()+1;

    // each of these compression schemes works just fine
    // but the 16bit quality is much nicer and with a local
    // install assured we can rely upon the sound memory
    // manager to do the right thing for us and page
    // sound in as needed

    if( sfx->soundCompressed == qtrue)
    {
        sfx->soundCompressionMethod = 1;
        sfx->soundData = NULL;
        sfx->soundLength = ResampleSfxRaw( samples, info.rate, info.width, info.samples, (data + info.dataofs) );
        S_AdpcmEncodeSound(sfx, samples);
#if 0
    }
    else if (info.samples>(SND_CHUNK_SIZE*16) && info.width >1)
    {
        sfx->soundCompressionMethod = 3;
        sfx->soundData = NULL;
        sfx->soundLength = ResampleSfxRaw( samples, info.rate, info.width, info.samples, (data + info.dataofs) );
        encodeMuLaw( sfx, samples);
    }
    else if (info.samples>(SND_CHUNK_SIZE*6400) && info.width >1)
    {
        sfx->soundCompressionMethod = 2;
        sfx->soundData = NULL;
        sfx->soundLength = ResampleSfxRaw( samples, info.rate, info.width, info.samples, (data + info.dataofs) );
        encodeWavelet( sfx, samples);
#endif
    }
    else
    {
        sfx->soundCompressionMethod = 0;
        sfx->soundLength = info.samples;
        sfx->soundData = NULL;
        ResampleSfx( sfx, info.rate, info.width, data + info.dataofs, qfalse );
    }

    Hunk_FreeTempMemory(samples);
    FS_FreeFile( data );

    return qtrue;
}

void S_DisplayFreeMemory()
{
    Com_Printf("%d bytes free sound buffer memory, %d total used\n", inUse, totalInUse);
}
